package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
	"sync"
	"time"

	"redisClient"

	"github.com/Unknwon/goconfig"
)

var conf *goconfig.ConfigFile
var CodeStream string
var CodeGroup string
var ResultStream string
var ResultGroup string
var consumer string
var unlimitedIPs []string

var HTTP_REQUEST_TIMEOUT = 2 * time.Second
var REDIS_STREAM_READ_TIMEOUT = 5 * time.Second

type Empty struct{}

const (
	// unit:ms
	MAX_TIME = 5000
	MIN_TIME = 100
	// unit:KB
	MAX_MEMORY = 256 * 1024
	MIN_MEMORY = 16 * 1024
	// example max size
	MAX_EXAMPLE_SIZE = 5
)

type BackupCodeInfo struct {
	Id          string 		`json:"id"`
	Code        string 		`json:"code"`
	Language    string 		`json:"language"`
	JudgeType   int    		`json:"judge_type"`
	ProblemId   string 		`json:"problem_id"`
	Stdin       []string	`json:"stdin"` 
	Stdout		[]string	`json:"stdout"`
	InitRefId	string		`json:"init_ref_id"`
	DataRefId   string 		`json:"data_ref_id"`
	SpjRefId    string 		`json:"spj_ref_id"`
	Time        int    		`json:"time"`
	Memory      int64  		`json:"memory"`
	Compare     int    		`json:"compare"`
	Eps         string 		`json:"eps"`
	Ruler       int    		`json:"ruler"`
	QuerySet	int	   		`json:"query_set"`
	ResponseUrl string 		`json:"response_url"`
}

var allowIPs map[string]Empty

func containIps(ip string) bool {
	_, ok := allowIPs[ip]
	return ok
}

func WriteStringToFile(filename, text string) string {
	f, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		log.Printf("open file error:%v", err)
		return "{\"code\":101,msg:\"打开文件失败\"}"
	}
	defer f.Close()
	n, err := f.WriteString(text)
	if err != nil {
		log.Printf("write file error:%v", err)
		return "{\"code\":102,msg:\"写入文件失败\"}"
	}
	if n != len(text) {
		log.Printf("write size file error:%v", err)
		return "{\"code\":103,msg:\"写入文件缺少字符\"}"
	}
	return ""
}

func ResponseResult(url string, data map[string]interface{}) string {
	c := http.Client{
		Timeout: time.Second * 2,
	}
	bytesData, err := json.Marshal(data)
	if err != nil {
		return err.Error()
	}
	req, err := http.NewRequest("POST", url, bytes.NewReader(bytesData))
	if err != nil {
		return err.Error()
	}
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	resp, err := c.Do(req)
	if err != nil {
		return err.Error()
	}
	defer resp.Body.Close()
	defer req.Body.Close()
	return resp.Status
}

type InsertInfo struct {
	Id    string `json:"id"`
	Msg   string `json:"msg"`
	Error string `json:"error"`
}

func anyToJsonStr(object interface{}) (string, error) {
	object_json, err := json.Marshal(object)
	return string(object_json), err
}

func addQueue(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		w.Header().Set("content-type", "application/json")
		body, err := ioutil.ReadAll(r.Body)

		// 打印接收到代码信息
		log.Print(string(body))
		insertInfo := new(InsertInfo)
		if err != nil {
			insertInfo.Error = err.Error()
			response, _ := anyToJsonStr(insertInfo)
			fmt.Fprint(w, response)
			return
		}
		codeInfo := new(BackupCodeInfo)
		if err = json.Unmarshal(body, &codeInfo); err != nil {
			insertInfo.Error = err.Error()
			response, _ := anyToJsonStr(insertInfo)
			fmt.Fprint(w, response)
			return
		}

		if len(codeInfo.Stdin) > MAX_EXAMPLE_SIZE {
			fmt.Fprint(w, `{"code":127,"msg":"example size must be less equal 5"}`)
			return
		}

		id := redisClient.RedisAddStream(CodeStream, map[string]interface{}{
			"codeInfo": string(body),
		})
		if id == "" {
			fmt.Fprint(w, `{"code":127,"msg":"代码进入队列失败"}`)
			return
		}

		fmt.Fprintf(w, `{"code":0,"msg":"","data":{"id":"%s", "check_url":"%s"}}`, id, check_result_url)
	}
}

func responseResult(key string, url string, id string, data string) {
	// 减少计数
	defer wg.Done()

	log.Printf("%v\n", map[string]interface{}{
		"url":  url,
		"id":   id,
		"data": data,
	})
	if url == "" {
		ok := redisClient.RedisAckGroup("url-"+url, ResultGroup, key)
		log.Printf("empty url, ignore")
		log.Println(ok)
		if ok > 0 {
			log.Printf("task ack done,id:%s\n", key)
		} else {
			log.Printf("task ack fail,id:%s\n", key)
		}
		return
	}
	status := ResponseResult(
		url,
		map[string]interface{}{
			"id":   id,
			"data": data,
			"code": 0,
		},
	)
	log.Println(status)
	if status == "200 OK" {
		log.Printf("result response ok,id:%s\n", key)
		// 拼接出stream名
		ok := redisClient.RedisAckGroup("url-"+url, ResultGroup, key)
		log.Println(ok)
		if ok > 0 {
			log.Printf("task ack done,id:%s\n", key)
		} else {
			log.Printf("task ack fail,id:%s\n", key)
		}
	} else {
		log.Printf("result response fail,id:%s\n", key)
	}

}

func checkUrl(url string) {

	// 减少计数
	defer wg.Done()

	// 先检查是否有发送失败的数据
	pendingMsgs := redisClient.RedisXPending(
		url,
		ResultGroup,
		"-",
		"+",
		10,
		consumer,
	)

	// 如果有失败的数据
	if len(pendingMsgs) != 0 {
		wg.Add(len(pendingMsgs))
		for _, pendingMsg := range pendingMsgs {
			key := pendingMsg.Id
			checkMsgs := redisClient.RedisXRange(url, key, key, 1)

			if len(checkMsgs) != 0 {
				msg := checkMsgs[0]
				log.Printf("get task,id:%s\n", key)
				go responseResult(key, url[4:],
					msg.Values["id"].(string), msg.Values["result"].(string))

			} else {
				// 丢失信息
				log.Printf("loss msg id:%s", key)
				wg.Done()
			}
		}

	} else {
		task := redisClient.RedisReadGroup(
			[]string{url, ">"},
			ResultGroup,
			consumer,
			10,
			REDIS_STREAM_READ_TIMEOUT,
		)
		if len(task) == 0 {
			// fmt.Println("no task")
			// continue
			return
		}
		message := task[0].Messages
		wg.Add(len(message))
		for i := 0; i < len(message); i++ {
			key := message[i].ID
			msg := message[i]
			log.Printf("get task,id:%s\n", key)
			go responseResult(key, url[4:],
				msg.Values["id"].(string), msg.Values["result"].(string))
		}
	}
}

var wg sync.WaitGroup

func checkResultAndResponse() {

	for {
		urlStreams := redisClient.RedisKeys("url-*")
		wg.Add(len(urlStreams))
		for _, url := range urlStreams {
			go checkUrl(url)
		}
		wg.Wait()
		time.Sleep(HTTP_REQUEST_TIMEOUT)
	}
}

func GetDockerId() string {
	//head -1 /proc/self/cgroup|cut -d/ -f3|cut -c1-12
	command := exec.Command("bash", "-c", "head -1 /proc/self/cgroup|cut -d/ -f3|cut -c1-12")
	command.Stdout = &bytes.Buffer{}
	command.Stderr = &bytes.Buffer{}
	err := command.Run()
	if err != nil {
		log.Fatalf("get container ID error:%v", err)
	}
	return command.Stdout.(*bytes.Buffer).String()
}

var logPath string
var myLogFile string
var myConfFile string
var check_result_url string
var redisSetPrefix string

func TimeToCreatLogFile() {
	for {
		now := time.Now()                                                                    //获取当前时间，放到now里面，要给next用
		next := now.Add(time.Hour * 24)                                                      //通过now偏移24小时
		next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location()) //获取下一个凌晨的日期
		t := time.NewTimer(next.Sub(now))                                                    //计算当前时间到凌晨的时间间隔，设置一个定时器
		<-t.C
		dirname := next.Format("2006-01-02")
		dirname = logPath + "/" + dirname
		err := os.Mkdir(dirname, 0755)
		if err != nil && !os.IsExist(err) {
			log.Fatalf("mkdir error:%v", err)
		}
		logFile, err := os.OpenFile(dirname+"/"+myLogFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0755)
		if err != nil && !os.IsExist(err) {
			panic(err)
		}
		log.SetOutput(logFile)
	}
}

func checkToken(token string) bool {
	return true
}

func checkResult(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		v := r.URL.Query()
		w.Header().Set("content-type", "application/json")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		id := v.Get("id")
		token := v.Get("Token")

		if id == "" || !checkToken(token) {
			fmt.Fprint(w, `{"code":10,"msg":"invalid params"}`)
			return
		}

		log.Printf(`check key:%s`, id)

		id = redisSetPrefix + id
		result := redisClient.RedisGet(id)
		if result != "" {
			fmt.Fprint(w, fmt.Sprintf(`{"code":0, "data":%s}`, result))
			return
		}

		id = id[len(redisSetPrefix):]

		if len(redisClient.RedisXPending(CodeStream, CodeGroup, id, id, 1, "")) > 0 {
			fmt.Fprint(w, `{"code":200,"msg":"执行中"}`)
			return
		}

		if len(redisClient.RedisXRange(CodeStream, id, id, 1)) > 0 {
			fmt.Fprint(w, `{"code":201,"msg":"排队中"}`)
			return
		}

		fmt.Fprint(w, `{"code":11,"msg":"查询id不存在"}`)
	}
}

func main() {
	var err error
	if len(os.Args) < 2 {
		fmt.Printf("args count must be equal 1")
		return
	}

	myConfFile = os.Args[1]

	conf, err = goconfig.LoadConfigFile(myConfFile)
	if err != nil {
		log.Fatal("ReadConfigure:", err)
	}
	myLogFile, err = conf.GetValue("BACKUP_INTERFACE", "BACKUP_INTERFACE_LOGFILE")
	if err != nil || myLogFile == "" {
		log.Fatal("ReadConfigure BACKUP_INTERFACE_LOGFILE error or param is null:", err)
	}

	logPath, err = conf.GetValue("BACKUP_INTERFACE", "BACKUP_INTERFACE_LOGFILE_PATH")
	if err != nil || logPath == "" {
		log.Fatal("ReadConfigure BACKUP_INTERFACE_LOGFILE_PATH error or param is null:", err)
	}

	check_result_url, err = conf.GetValue("WEB", "CHECK_RESULT_URL")
	if err != nil {
		log.Fatal("ReadConfigure CHECK_RESULT_URL:", err)
	}

	logPath = logPath + "/backup_interface"

	dirname := time.Now().Format("2006-01-02")
	dirname = logPath + "/" + dirname
	err = os.MkdirAll(dirname, 0755)
	if err != nil && !os.IsExist(err) {
		log.Fatalf("mkdir error:%v", err)
	}
	logFile, err := os.OpenFile(dirname+"/"+myLogFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0755)
	if err != nil && !os.IsExist(err) {
		panic(err)
	}
	log.SetOutput(logFile)

	consumer = GetDockerId()

	conf, err = goconfig.LoadConfigFile(myConfFile)
	if err != nil {
		log.Fatal("ReadConfigure:", err)
	}

	go TimeToCreatLogFile()

	ips, err := conf.GetValue("WEB", "ALLOW_IPS")
	if err != nil && ips != "" {
		ipArray := strings.Split(ips, ",")
		for _, v := range ipArray {
			if v != "" {
				allowIPs[v] = Empty{}
			}
		}
	}

	CodeStream, err = conf.GetValue("REDIS", "REDIS_STREAM_CODE")
	if err != nil {
		log.Fatal("ReadConfigure STREAM XUJCOJ:", err)
	}
	CodeGroup, err = conf.GetValue("REDIS", "REDIS_GROUP_CODE")
	if err != nil {
		log.Fatal("ReadConfigure GROUP XUJCOJ:", err)
	}
	if err != nil {
		log.Fatal("切换工作路径出错:", err.Error())
	}
	host, _ := conf.GetValue("REDIS", "REDIS_HOST")
	port, _ := conf.GetValue("REDIS", "REDIS_PORT")
	pwd, _ := conf.GetValue("REDIS", "REDIS_PWD")
	db, _ := conf.Int("REDIS", "REDIS_DATABASE")
	redisSetPrefix, _ = conf.GetValue("REDIS", "REDIS_SET_PREFIX")
	webPort, err := conf.Int("WEB", "WEB_PORT")
	ResultGroup, err = conf.GetValue("REDIS", "REDIS_GROUP_RESPONSE")
	if err != nil {
		log.Fatal("ReadConfigure GROUP:", err)
	}
	redisClient.InitClient(host, port, pwd, db)
	// 上传代码到消息队列
	http.HandleFunc("/addQueue", addQueue)
	http.HandleFunc("/check", checkResult)
	go checkResultAndResponse()
	err = http.ListenAndServe(fmt.Sprintf(":%d", webPort), nil) //监听端口
	if err != nil {
		log.Fatal("ListenAndServer:", err)
	}
}
